package com.inepex.inechart.client.linechart;

import java.util.ArrayList;
import java.util.List;

import com.inepex.inechart.shared.data.KeyValueDataEntry;
import com.inepex.inechart.shared.data.KeyValueDataSet;
import com.inepex.inechart.shared.linechart.Curve;
import com.inepex.inechart.shared.linechart.LineChartModelAssist;
import com.inepex.inechart.shared.linechart.Point;
import com.inepex.inechart.shared.misc.SearchParameter;
import com.inepex.inegraphics.impl.client.IncrementalProgress;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class LineChartModelAssistGWT
extends LineChartModelAssist 
implements IncrementalProgress{

	protected static final double calcFilterDurationRatio = 2 / 5;
	protected static final int minPointCountToCalc = 4;

	protected IncrementalProgressCaller updater;
	protected Curve curve;
	protected List<KeyValueDataEntry> entriesToCalcRemaining;
	protected int totalEntryCount;
	protected int readyEntryCount;
	protected double from;
	protected double to;
	protected long updatePartStart;
	protected KeyValueDataEntry beforeMin = null;
	protected KeyValueDataEntry beforeMax = null;
	protected KeyValueDataEntry afterMax = null;
	protected KeyValueDataEntry afterMin = null;
	protected boolean searchedExtremesOutsideRange;
	protected ArrayList<Point> filteredPointsInRange;
	protected CurvePart curvePart;
	protected LineChartGwt lineChartGwt;

	public LineChartModelAssistGWT(LineChartGwt lineChartGwt) {
		super(lineChartGwt.getCartesianAssist());
		filteredPointsInRange = new ArrayList<Point>();
		this.lineChartGwt = lineChartGwt;
	}

	public void clearCurve(Curve curve){
		getDiscontinuitiesAsPoints(curve).clear();
		getPoints(curve).clear();
		getEntryPointMap(curve).clear();
	}

	@Override
	public void initProgress(IncrementalProgressCaller updater) {
		this.updater = updater;
		readyEntryCount = 0;
		entriesToCalcRemaining = curve.getDataSet().getEntriesByKey(from, to);
		filteredPointsInRange.clear();
		totalEntryCount = entriesToCalcRemaining.size();
		searchedExtremesOutsideRange = false;
		curvePart = new CurvePart(from, to);
	}

	@Override
	public boolean nextProgressPart() {
		if(totalEntryCount == 0){
			return false;
		}
		updatePartStart = System.currentTimeMillis();
		if(!searchedExtremesOutsideRange){
			searchYExtremesOutsideRange();
		}
		doPartialPointCalcOnSortedCurve();
		boolean finished = totalEntryCount > readyEntryCount;
		if(finished){
			Point p = null;
			if(beforeMin != null){
				p = createDataPoint(beforeMin, true);
				addPointToCurvesContainers(p);
				getPoints(curve).add(p);
			}
			curvePart.setBeforeMin(p);
			p = null;
			if(beforeMax != null){
				p = createDataPoint(beforeMax, true);
				addPointToCurvesContainers(p);
				getPoints(curve).add(p);
			}
			curvePart.setBeforeMax(p);
			p = null;
			getPoints(curve).addAll(filteredPointsInRange);
			curvePart.setInsidePoints(filteredPointsInRange);
			if(afterMin != null){
				p = createDataPoint(afterMin, true);
				addPointToCurvesContainers(p);
				getPoints(curve).add(p);
			}
			curvePart.setAfterMin(p);
			p = null;
			if(afterMax != null){
				p = createDataPoint(afterMax, true);
				addPointToCurvesContainers(p);
				getPoints(curve).add(p);
			}
			curvePart.setAfterMax(p);
			KeyValueDataEntry justBef = curve.getDataSet().searchByKey(from, SearchParameter.exact_or_lower);
			if(justBef != null && justBef.getValuePair()[0] != from){
					curvePart.setJustBefore(createDataPoint(justBef, true));
			}
			else{
				curvePart.setJustBefore(null);
			}
			KeyValueDataEntry justAft = curve.getDataSet().searchByKey(to, SearchParameter.exact_or_higher);
			if(justAft != null && justAft.getValuePair()[0] != to){
				curvePart.setJustAfter(createDataPoint(justAft, true));
			}
			else{
				curvePart.setJustAfter(null);
			}
			lineChartGwt.curveModelMap.get(curve).addCurvePart(curvePart);
		}
		return finished;
	}


	@Override
	public void cancelProgress() {
		entriesToCalcRemaining.clear();
		filteredPointsInRange.clear();
	}

	@Override
	public double getProgressRate() {
		return readyEntryCount / (double) totalEntryCount;
	}

	protected void searchYExtremesOutsideRange(){
		KeyValueDataEntry[] befExtr = KeyValueDataSet.searchValueExtremes(curve.getDataSet().getEntriesBeforeKey(from));
		KeyValueDataEntry[] aftExtr = KeyValueDataSet.searchValueExtremes(curve.getDataSet().getEntriesAfterKey(to));
		if(befExtr != null){
			beforeMin = befExtr[0];
			beforeMax = befExtr[1];
		}
		if(befExtr != null){
			afterMin = aftExtr[0];
			afterMax = aftExtr[1];
		}
		searchedExtremesOutsideRange = true;
	}

	protected void doPartialPointCalcOnSortedCurve(){
		ArrayList<Point> actualCalcedPoints = new ArrayList<Point>();
		int calcedEntryCount = 0;
		for (KeyValueDataEntry actualEntry : entriesToCalcRemaining) {
			if(calcedEntryCount++ >= minPointCountToCalc && !canCalcMorePoints()){
				break;
			}
			Point actualCalced = createDataPoint(actualEntry, getUnfilterableEntries(curve).contains(actualEntry));
			addPointToCurvesContainers(actualCalced);
			actualCalcedPoints.add(actualCalced);
		}
		entriesToCalcRemaining = entriesToCalcRemaining.subList(calcedEntryCount-1, entriesToCalcRemaining.size());
		if(pointFilter != null){
			filteredPointsInRange.addAll(pointFilter.filterDataPoints(actualCalcedPoints));
		}
		readyEntryCount += calcedEntryCount;
	}

	protected boolean canCalcMorePoints(){
		return System.currentTimeMillis() - updatePartStart < updater.getProgressPartTresholdMS() * calcFilterDurationRatio;
	}

	protected void addPointToCurvesContainers(Point point){
		if(getDiscontinuityEntries(curve).contains(point.getEntry())){
			getDiscontinuitiesAsPoints(curve).add(point);
		}
		getEntryPointMap(curve).put(point.getEntry(), point);
	}

	public void setCurve(Curve curve, double from, double to) {
		this.curve = curve;
		this.from = from;
		this.to = to;
	}


}
